﻿#include "serialport.h"
#include <Windows.h> 
#include "type_cpp14.h"
#include <string>
#include <iostream>
#include <algorithm>
#include <cctype>

namespace Serial_Port {

	class SerialPort::Impl {
	private:
		HANDLE  port = INVALID_HANDLE_VALUE;
		CRITICAL_SECTION   cs;
		int system_error_code;
		SerialPort* parent;
	public:
		char port_name[32] = "";
		int baud = 115200;
		Impl(SerialPort* p) {
			parent = p;
			InitializeCriticalSection(&cs);
		}
		~Impl() {
			closePort();
		}

		inline bool isPortOpen() {
			if (port == INVALID_HANDLE_VALUE) {
				return false;
			} else {
				return true;
				//todo: add more check here
			}
		}
		error_code setPort(char const* ch) {
			if (isPortOpen()) {
				return set_name_while_port_opened;
			} else {
				std::string str(ch);
				//检查串口名字是否正确
				//先将字符转换为大写
				std::transform(str.begin(), str.end(), str.begin(), ::toupper);
				//检查字符串是否以COM开头
				if (str.find("COM") != 0) {
					return setting_port_fail;
				}
				//检查串口号是否在1-255之间
				int port_num = std::stoi(str.substr(3));
				if (port_num < 1 || port_num > 255) {
					return setting_port_fail;
				}
				//如果串口号大于9，则串口名字为\\.\COMx
				if (port_num > 9) {
					sprintf_s(port_name, "\\\\.\\COM%d", port_num);
				} else {
					sprintf_s(port_name, "COM%d", port_num);
				}
				return done;
			}
		}
		error_code setBaud(int baud) {
			if (isPortOpen()) {
				return set_name_while_port_opened;
			} else {
				this->baud = baud;
				return done;
			}
		}
		error_code openPort() {
			if (isPortOpen()) {
				return redo;
			}
			bool is_success = false;
			int error_num = 0;
			EnterCriticalSection(&cs);
			port = CreateFileA(port_name,
				GENERIC_READ | GENERIC_WRITE,
				0,
				NULL,
				OPEN_EXISTING,
				0,
				NULL);
			if (port == INVALID_HANDLE_VALUE) {
				DWORD dwError = GetLastError();
				//printf("CreateFile failed with error %d.\n", dwError);
				LeaveCriticalSection(&cs);
				return open_port_fail;
			}
			if (false == SetupComm(port, 2048, 2048)) {
				LeaveCriticalSection(&cs);
				system_error_code = GetLastError();
				return system_error;
			}
			COMMTIMEOUTS  port_CTO = { 0,0,0,0,0 };
			if (false == SetCommTimeouts(port, &port_CTO)) {
				LeaveCriticalSection(&cs);
				system_error_code = GetLastError();
				return system_error;
			}
			DCB  dcb;
			if (false == GetCommState(port, &dcb)) {
				LeaveCriticalSection(&cs);
				system_error_code = GetLastError();
				return system_error;
			}
			dcb.BaudRate = baud;
			dcb.ByteSize = 8;
			dcb.Parity = 0;
			dcb.StopBits = 1;

			if (false == SetCommState(port, &dcb)) {
				LeaveCriticalSection(&cs);
				system_error_code = GetLastError();
				return system_error;
			}

			LeaveCriticalSection(&cs);
			return done;
		}

		error_code send(byte const* b, int length) {
			if (isPortOpen() == false) {
				return send_while_port_closed;
			}
			EnterCriticalSection(&cs);
			DWORD  BytesToSend = 0;
			if (false == WriteFile(port, b, length, &BytesToSend, NULL)) {
				system_error_code = GetLastError();
				PurgeComm(port, PURGE_RXCLEAR | PURGE_RXABORT);
				LeaveCriticalSection(&cs);
				return system_error;
			}
			LeaveCriticalSection(&cs);
			return done;
		}



		error_code recv(byte* b, int& byte_received, int max_length) {
			if (isPortOpen() == false) {
				return recv_while_port_closed;
			}
			EnterCriticalSection(&cs);
			COMSTAT sta;
			DWORD dwError = 0;
			if (false == ClearCommError(port, &dwError, &sta)) {
				system_error_code = dwError;
				PurgeComm(port, PURGE_RXCLEAR | PURGE_RXABORT);
				LeaveCriticalSection(&cs);
				return system_error;
			}
			byte_received = sta.cbInQue;

			if (byte_received == 0) {
				LeaveCriticalSection(&cs);
				return done;
			}
			if (byte_received > max_length) {
				byte_received = max_length;
			}
			DWORD recv_count;
			if (false == ReadFile(port, b, byte_received, &recv_count, NULL)) {
				system_error_code = GetLastError();
				LeaveCriticalSection(&cs);
				return system_error;
			}
			if (byte_received != recv_count) {
				LeaveCriticalSection(&cs);
				return bad_receive_len;
			}
			LeaveCriticalSection(&cs);
			return done;
		}

		error_code closePort() {
			if (port != INVALID_HANDLE_VALUE) {
				if (false == CloseHandle(port)) {
					system_error_code = GetLastError();
					return system_error;
				} else {
					port = INVALID_HANDLE_VALUE;
					return done;
				}
			} else {
				return redo;
			}
		}

	};



	SerialPort::SerialPort() :pimpl(std::make_unique<Impl>(this)) {}
	SerialPort::~SerialPort() = default;
	
	bool SerialPort::isRunning() {
		return pimpl->isPortOpen();
	}
	error_code SerialPort::setPort(char const* ch) {
		return pimpl->setPort(ch);
	}
	error_code SerialPort::setBaud(int baud) {
		return pimpl->setBaud(baud);
	}
	error_code SerialPort::openPort() {
		return pimpl->openPort();
	}
	error_code SerialPort::closePort() {
		return pimpl->closePort();
	}
	error_code SerialPort::send(byte const* b, int length) {
		return pimpl->send(b, length);
	}
	error_code SerialPort::recv(byte* b, int& byte_received, int max_length) {
		return pimpl->recv(b, byte_received, max_length);
	}


}